﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Xml;
using CSharpExt.Collections;
using CSharpExt.Text;
using CSharpExt.Xml;
using log4net;

namespace CSharpExt.Time.Scheduler
{
    /// <summary>
    /// Questa classe ha il compito di leggere un file di configurazione, oppure
    /// il tag <Schedules> all'interno del file di configurazione di un profilo e
    /// registrare le schedulazioni
    /// </summary>
    public class SchedulerConfigurator
    {
        #region Static Operations

        public static void Configure(Scheduler scheduler, String configFile)
        {
            new SchedulerConfigurator(scheduler).Configure(configFile);
        }

        public static void Configure(Scheduler scheduler, XmlElement config)
        {
            new SchedulerConfigurator(scheduler).Configure(config);
        }

        #endregion

        #region Private Data

        private static ILog _logger = LogManager.GetLogger(typeof(SchedulerConfigurator));

        private Dictionary<String, Action<XmlElement>> _handlers = new Dictionary<String, Action<XmlElement>>();
        private Dictionary<String, Object> _customData = new Dictionary<String, Object>();

        protected Scheduler _scheduler;
        protected Schedule _schedule;

        #endregion

        #region Constructor

        protected SchedulerConfigurator(Scheduler s)
        {
            _scheduler = s;

            RegisterHandler("OneTime", AddOneTimeTrigger);
            RegisterHandler("Daily", AddDailyTrigger);
            RegisterHandler("Weekly", AddWeeklyTrigger);
            RegisterHandler("Monthly", AddMonthlyTrigger);
            RegisterHandler("Cron", AddCronTrigger);

            RegisterHandler("Log", AddLogTask);
        }

        #endregion

        #region Implementation (Tasks)

        public SchedulerConfigurator RegisterHandler(String tag, Action<XmlElement> handler)
        {
            _handlers[tag] = handler;
            return this;
        }

        private void AddLogTask(XmlElement node)
        {
            Task t = new LogTask();
            t.Configuration = node;

            _schedule.AddTask(t);
        }

        #endregion

        #region Operations

        public void Configure(String configFile)
        {
            XmlDocument doc = DocumentResolver.LoadAndResolve(configFile);
            XmlElement config = (XmlElement)doc.SelectSingleNode("/Schedules");
            Configure(config);
        }

        public void Configure(XmlElement config)
        {
            foreach (XmlElement schedule in config.SelectNodes("Schedule"))
            {
                _schedule = new Schedule();

                foreach (XmlNode node in schedule.ChildNodes)
                {
                    if (node.NodeType != XmlNodeType.Element)
                        continue;

                    Action<XmlElement> handler;
                    if (_handlers.TryGetValue(node.Name, out handler))
                    {
                        handler((XmlElement)node);
                    }
                }

                _scheduler.AddSchedule(_schedule);
            }
        }

        #endregion

        #region Custom Data

        public SchedulerConfigurator SetData(String name, Object data)
        {
            _customData[name] = data;
            return this;
        }

        public Object GetData(String name, Object defaultData)
        {
            Object data;
            if (_customData.TryGetValue(name, out data))
                return data;
            else
                return defaultData;
        }

        #endregion

        #region Implementation (Triggers)

        /*
         * <OneTime when="yyyy/mm/dd HH:MM:SS" date="yyyy/mm/dd" time="HH:MM:SS"/>
         */
        private void AddOneTimeTrigger(XmlElement node)
        {
            DateTime when = getDateTime(node, "when", "date", "time");
            Trigger t = new OneTimeTrigger()
            {
                When = when
            };

            _schedule.AddTrigger(t);
        }

        /*
         * <Daily
            when="yyyy/mm/dd HH:MM:SS" date="yyyy/mm/dd" time="HH:MM:SS"
            repeatEach="dd,..."
            end="yyyy/mm/dd HH:MM:SS"   endDate="yyyy/mm/dd"   endTime="HH:MM:SS"/>
         */
        private void AddDailyTrigger(XmlElement node)
        {
            DateTime when = getDateTime(node, "when", "date", "time");
            DateTime endDate = getDateTime(node, "end", "endDate", "endTime");
            int repeatEach = getNumber(node, "repeatEach");

            Trigger t = new DailyTrigger()
            {
                When = when,
                EndDate = endDate,
                RepeatEach = repeatEach
            };

            _schedule.AddTrigger(t);
        }

        /*
         * <Weekly
            when="yyyy/mm/dd HH:MM:SS" date="yyyy/mm/dd" time="HH:MM:SS"
            recurEvery="w,..."
            weekDays="wd,..."
            end="yyyy/mm/dd HH:MM:SS"   endDate="yyyy/mm/dd"   endTime="HH:MM:SS" />
         */
        private void AddWeeklyTrigger(XmlElement node)
        {
            DateTime when = getDateTime(node, "when", "date", "time");
            DateTime endDate = getDateTime(node, "end", "endDate", "endTime");
            int repeatEach = getNumber(node, "repeatEach");
            BitSet weekDays = getWeekDays(node, "weekDays");

            Trigger t = new WeeklyTrigger()
            {
                When = when,
                EndDate = endDate,
                RepeatEach = repeatEach,
                WeekDays = weekDays
            };

            _schedule.AddTrigger(t);
        }

        /*
         * <Monthly
            when="yyyy/mm/dd HH:MM:SS"  date="yyyy/mm/dd" time="HH:MM:SS"
            repeatEach="m"
            months="mm,..."
            days="dd,..."
            end="yyyy/mm/dd HH:MM:SS"   endDate="yyyy/mm/dd"   endTime="HH:MM:SS" />
         */
        private void AddMonthlyTrigger(XmlElement node)
        {
            DateTime when = getDateTime(node, "when", "date", "time");
            DateTime endDate = getDateTime(node, "end", "endDate", "endTime");
            BitSet months = getMonths(node, "months");
            BitSet days = getNumbers(node, "days", 31, 1);
            int repeatEach = getNumber(node, "repeatEach");

            Trigger t = new MonthlyTrigger()
            {
                When = when,
                EndDate = endDate,
                Months = months,
                Days = days,
                RepeatEach = repeatEach
            };

            _schedule.AddTrigger(t);
        }

        /*
         * <Cron
            start="yyyy/mm/dd HH:MM:SS" startDate="yyyy/mm/dd" startTime="HH:MM:SS"
            seconds="SS,..."
            minutes="MM,..."
            hours="HH,..."
            days="dd,..."
            months="mm,..."
            on="wd,..."
            end="yyyy/mm/dd HH:MM:SS"   endDate="yyyy/mm/dd"   endTime="HH:MM:SS" />
         */
        private void AddCronTrigger(XmlElement node)
        {
            DateTime when = getDateTime(node, "when", "date", "time");
            DateTime endDate = getDateTime(node, "end", "endDate", "endTime");
            BitSet seconds = getSeconds(node, "seconds");
            BitSet minutes = getNumbers(node, "minutes", 60, 0);
            BitSet hours = getNumbers(node, "hours", 24, 0);
            BitSet months = getMonths(node, "months");
            BitSet weekDays = getWeekDays(node, "weekDays");

            Trigger t = new CronTrigger()
            {
                When = when,
                EndDate = endDate,
                Seconds = seconds,
                Minutes = minutes,
                Hours = hours,
                Months = months,
                WeekDays = weekDays
            };

            _schedule.AddTrigger(t);
        }

        #endregion

        #region Implementation

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="dtTag">datetime tag</param>
        /// <param name="dTag">date tag</param>
        /// <param name="tTag">time tag</param>
        /// <returns></returns>
        private DateTime getDateTime(XmlElement node, String dtName, String dName, String tName)
        {
            String dts = node.GetAttribute(dtName);
            if (!String.IsNullOrEmpty(dts))
                return DateTimeParser.Parse(dts);

            String ds = node.GetAttribute(dName);
            String ts = node.GetAttribute(tName);
            return DateTimeParser.Parse(ds, ts);
        }

        private int getNumber(XmlElement node, String tag)
        {
            return node.GetAttributeValue(tag, 1);
        }

        private BitSet getWeekDays(XmlElement node, String tag)
        {
            String weekDays = node.GetAttributeValue(tag, "*");
            return BitSet.Parse(weekDays, BitSet.WEEKDAY_NAMES);
        }

        private BitSet getMonths(XmlElement node, String tag)
        {
            String months = node.GetAttributeValue(tag, "*");
            return BitSet.Parse(months, BitSet.MONTH_NAMES, 1);
        }

        private BitSet getSeconds(XmlElement node, String tag)
        {
            String monthDays = node.GetAttributeValue(tag, "0");
            return BitSet.Parse(monthDays, 60, 0);
        }

        private BitSet getNumbers(XmlElement node, String tag, int length, int start)
        {
            String monthDays = node.GetAttributeValue(tag, "*");
            return BitSet.Parse(monthDays, length, start);
        }

        #endregion

        #region Interface

        /*
         * <tag >
         *      <Param name="..." value="..."/>
         *      ...
         * </tag>
         */
        public static StringDictionary GetParameters(XmlElement node)
        {
            StringDictionary args = new StringDictionary();

            foreach (XmlElement param in node.SelectNodes("Param"))
            {
                String name = param.GetAttribute("name");
                if (String.IsNullOrEmpty(name))
                    continue;

                String value = param.GetAttribute("value");
                args[name] = value;
            }

            return args;
        }

        #endregion
    }
}
